﻿#region Using Statements
using System;
using GameBase.Screens;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace StarFox.Screens
{
    #region Delegates

    /// <summary>
    /// References a method to be called when performing an asset loading operation.
    /// </summary>
    /// <param name="contentManager">ContentManager where to load assets from.</param>
    /// <param name="assetIndex">Index of the asset to be loaded.</param>
    /// <returns>True if all assets have been loaded.</returns>
    public delegate bool LoadAssetCallback(ContentManager contentManager, int assetIndex);

    #endregion

    #region Types

    /// <summary>
    /// Defines how long loading a will take.
    /// </summary>
    public enum LoadTime
    {
        /// <summary>
        /// Loading will be fast, about one second.
        /// </summary>
        FAST,
        /// <summary>
        /// Loading will be slow, about five seconds.
        /// </summary>
        SLOW
    };

    #endregion

    /// <summary>
    /// Basic functionality needed to load new screens.
    /// </summary>
    public class LoadingScreen : StarFoxScreen
    {
        #region Fields

        // screen loading fields
        Screen screenToLoad;
        bool toLoad = false;
        bool isLoaded = false;

        // sprite font
        SpriteFont bigFont;

        // loading messsage
        Vector2 initPosMsg;
        Vector2 endPosMsg;
        Vector2 currentPosMsg;

        // typing effect
        readonly string loadMsg = "LOADING";
        int tpLength = 0;
        bool toType = false;
        bool toWait = false;
        readonly TimeSpan totalTypeTime = TimeSpan.FromMilliseconds(750);
        readonly TimeSpan totalWaitTime = TimeSpan.FromMilliseconds(750);
        float tpTime = 0.0f;
        readonly int totalTypeCycles;
        int tpCycles = 0;

        // asset loading fields
        int assetIndex = 0;
        bool allAssetsLoaded = false;
        LoadAssetCallback loadAssetCallback;

        #endregion

        #region Screen Transitions

        // indicates whether a transition is taking place
        bool toVisible = false;
        bool toHidden = false;

        /// <summary>
        /// Makes the Screen transition to visible state.
        /// </summary>
        public override void Show()
        {
            toVisible = true;

            // show the loading message entirely
            tpLength = loadMsg.Length;
        }

        /// <summary>
        /// Makes the Screen transition to hidden state.
        /// </summary>
        public override void Hide()
        {
            toHidden = true;
        }

        // normalized transition time
        float trTime = 0.0f;

        #endregion

        #region Initialization

        // private constructor
        private LoadingScreen(ScreenManager screenManager, Screen screenToLoad, LoadTime loadTime, LoadAssetCallback callback)
            : base(screenManager)
        {
            Name = "LoadingScreen";

            // loading screen
            this.screenToLoad = screenToLoad;

            // loading time... how many times the loading message will be shown
            switch (loadTime)
            {
                case LoadTime.FAST:
                    totalTypeCycles = 1;
                    break;
                case LoadTime.SLOW:
                    totalTypeCycles = 3;
                    break;
                default:
                    break;
            }

            // asset loading
            loadAssetCallback = callback;
        }

        /// <summary>
        /// Called when the Screen needs to be initialized.
        /// </summary>
        public override void Initialize()
        {
            #region Global Content Loading

            bigFont = Game.Content.Load<SpriteFont>(ContentSettings.GetAsset("FONT_BIG"));

            #endregion

            #region Sprites

            // loading message position
            initPosMsg = new Vector2(Width, Height * 0.885f);
            endPosMsg = new Vector2(Width * 0.75f, initPosMsg.Y);
            currentPosMsg = initPosMsg;

            #endregion

            base.Initialize();
        }

        /// <summary>
        /// Loads a new Screen and unloads all the previous ones.
        /// </summary>
        /// <param name="screenManager">ScreenManager that this screen is associated to.</param>
        /// <param name="screenToLoad">New screen that will be loaded.</param>
        /// <param name="loadTime">Time necessary to load the new screen.</param>
        /// <param name="callback">A LoadAssetCallback that represents the method called when assets need to be loaded.</param>
        public static void Load(ScreenManager screenManager, Screen screenToLoad, LoadTime loadTime, LoadAssetCallback callback)
        {
            // create a loading screen
            LoadingScreen loadingScreen = new LoadingScreen(screenManager, screenToLoad, loadTime, callback);

            // loading an important screen...
            // ... the program won't return to previous screens for a while... remove them
            // ... add the loading screen to the stack
            screenManager.Clear(loadingScreen);
        }

        #endregion

        #region Update

        /// <summary>
        /// Called when the Screen needs to be updated.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        public override void Update(GameTime gameTime)
        {
            #region Screen Transition

            if (toVisible)
            {
                // update item positions
                float auxTrTime = 1.0f - (float)Math.Pow(1.0f - trTime, 2);
                // loading message
                currentPosMsg.X = initPosMsg.X - Math.Abs(initPosMsg.X - endPosMsg.X) * auxTrTime;

                // update transition timer
                float trDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds / Time.TotalMilliseconds);
                trTime += trDelta;
                trTime = MathHelper.Clamp(trTime, 0.0f, 1.0f);
                if (trTime == 1.0f) { toVisible = false; toLoad = true; toType = true; toWait = false; tpLength = 0; trTime = 0.0f; }
            }
            else if (toHidden)
            {
                // update item positions
                float auxTrTime = (float)Math.Pow(trTime, 2);
                // loading message
                currentPosMsg.X = endPosMsg.X + Math.Abs(initPosMsg.X - endPosMsg.X) * auxTrTime;

                // update transition timer
                float trDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds / Time.TotalMilliseconds);
                trTime += trDelta;
                trTime = MathHelper.Clamp(trTime, 0.0f, 1.0f);
                if (trTime == 1.0f) { toHidden = false; trTime = 0.0f; }
            }

            #endregion

            #region Per-Screen Content Loading

            if (!allAssetsLoaded)
            {
                if (loadAssetCallback(this.Content, assetIndex++))
                {
                    // all assets loaded
                    allAssetsLoaded = true;
                }
            }

            #endregion

            #region Screen Loading

            if (toLoad && !isLoaded)
            {
                // type the loading message
                if (toType)
                {
                    float tpDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds / (totalTypeTime.TotalMilliseconds / loadMsg.Length));
                    tpTime += tpDelta;
                    tpTime = MathHelper.Clamp(tpTime, 0.0f, 1.0f);
                    if (tpTime == 1.0f)
                    {
                        tpLength++;
                        tpTime = 0.0f;
                        if (tpLength == loadMsg.Length) { toType = false; toWait = true; }
                    }
                }
                // wait for a while showing the loading message entirely
                else if (toWait)
                {
                    float tpDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds / totalWaitTime.TotalMilliseconds);
                    tpTime += tpDelta;
                    tpTime = MathHelper.Clamp(tpTime, 0.0f, 1.0f);
                    if (tpTime == 1.0f)
                    {
                        tpCycles++;
                        if (tpCycles == totalTypeCycles)
                        { 
                            isLoaded = true; 
                            ScreenManager.Clear(screenToLoad); 
                        }
                        else 
                        {
                            tpTime = 0.0f; 
                            tpLength = 0;
                            toType = true; 
                            toWait = false;
                        }
                    }
                }
            }

            #endregion
        }

        #endregion

        #region Draw

        /// <summary>
        /// Called when 2D content of the Screen needs to be drawn.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to this method.</param>
        protected override void Draw2D(GameTime gameTime)
        {
            GraphicsDevice.SetRenderTarget(DisplayBuffer);
            GraphicsDevice.Clear(Color.Transparent);

            SpriteBatch.Begin();

            // loading message
            SpriteBatch.DrawString(bigFont, loadMsg.Substring(0, tpLength), currentPosMsg, Color.White, 0f, Vector2.Zero, Scale, SpriteEffects.None, 0f);

            SpriteBatch.End();

            GraphicsDevice.SetRenderTarget(null);
        }

        #endregion
    }
}